Declare property defaults so unset optional properties have a value - #260
Merged
Conversation
Several resource types documented a default in prose without declaring it, and the ones that did declare a default had no effect because Radius never materialized them. Declare the missing keys so they take effect alongside radius-project/radius schema defaulting. Added: size on redisCaches and postgreSqlDatabases, version and database on mySqlDatabases, and database on postgreSqlDatabases. neo4jDatabases is left alone because its documented default is the resource name, which a static default cannot express. The mySqlDatabases version default is 8.4, which is what the AWS Terraform and Kubernetes Bicep recipes already fall back to when version is unset. The Azure pack maps everything except 5.7 to 8.0.21, so it deploys the same image either way and only AWS and Kubernetes would notice a different value. Declaring 8.0 here would have silently downgraded them, and a running RDS instance rejects a major version downgrade outright. Related to radius-project/radius#12532 Signed-off-by: Mike Azure <127820851+AzureMike@users.noreply.github.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
kachawla
approved these changes
Jul 31, 2026
Signed-off-by: Mike Azure <127820851+AzureMike@users.noreply.github.com>
Reshrahim
approved these changes
Jul 31, 2026
pull Bot
pushed a commit
to TheTechOddBug/radius
that referenced
this pull request
Aug 1, 2026
…dius-project#12563) ## Summary A resource type schema can declare a `default` for a property, and Radius never applied it. `schema.ApplyDefaults` fills absent properties from the default declared in the schema, and a new `UpdateFilter` on the dynamic resource PUT path runs it before the resource is saved. Explicit values are never overwritten, and required properties are never defaulted, so leaving one out still fails validation instead of being quietly satisfied. ## Reason for change Fixes radius-project#12532 Deploying a resource without setting an optional property puts a literal template expression into the ARM request. The Azure recipe pack writes parameters as expressions over the resource: ``` skuName: '{{context.resource.properties.size == "S" ? "Balanced_B0" : "Balanced_B1"}}' ``` Leave `size` unset and there's nothing to substitute, so the raw `{{...}}` string is handed to ARM as the SKU name and rejected against `allowedValues`. The cause sits a level below the recipe. Validation checked a resource against its schema without filling anything in, so every `default:` key in every resource type has been inert — including the ones `objectStorage`, `mongoDatabases`, `models`, `kafka` and `rabbitMQ` already declare. With the defaults from radius-project/resource-types-contrib#260 declared, the existing recipe expressions resolve on their own, and neither the resolver nor the recipe pack needs to change. That covers ten optional properties across the Azure pack, not just `size`: | Type | Property | Unset today | With defaults | | --- | --- | --- | --- | | `redisCaches` | `size` | leaks | `Balanced_B0` | | `postgreSqlDatabases` | `size` | leaks | `Standard_B1ms` / `Burstable` | | `mySqlDatabases` | `version` | leaks | `8.0.21` | | `models` | `model` | leaks | `gpt-5-mini` | | `mongoDatabases` | `database` | leaks | `mongo_db` | | `mySqlDatabases` | `database` | leaks | `mysql_db` | | `postgreSqlDatabases` | `database` | leaks | `postgres_db` | | `rabbitMQ` | `queue` | leaks | `jobs` | | `kafka` | `topic` | leaks | `events` | | `objectStorage` | `containerName` | leaks | `data` | Six of those defaults already existed, so half the fix is Radius finally reading what contrib already wrote down. Two things reviewers should know. The filter sits ahead of the encryption filter, so a defaulted sensitive field is still encrypted. And defaults are reapplied on every write rather than carried forward from the previous version of the resource, which is what full-replace PUT semantics ask for — the consequence is that changing a declared default later changes what an existing resource gets on its next deployment, so a default is part of a resource type's API contract. ## How to test `go test ./pkg/schema/... ./pkg/dynamicrp/...` covers the new code directly. `pkg/schema/defaults_test.go` asserts that explicit values survive, required properties are never defaulted, read-only properties are skipped, absent nested objects aren't created, and map and slice defaults are deep copied so one resource can't mutate what the next one sees. End to end, all ten optional properties above were run through `ApplyDefaults` and the parameter resolver against the real contrib schemas and the real `recipepack/azure/aks-recipepack.bicep` expressions. Every one emits a literal `{{...}}` without defaults and resolves to the value in the table with them, on the unmodified resolver and the unmodified recipe expressions. `go vet` and `gofmt` are clean, and `pkg/schema/...`, `pkg/dynamicrp/...`, `pkg/recipes/...`, `pkg/cli/manifest/...` and `pkg/ucp/integrationtests/resourceproviders/...` all pass, including after rebasing onto the kin-openapi 0.144.0 bump in radius-project#12561. Built-in resource types pick this up once radius-project/resource-types-contrib#260 merges and `make update-resource-types` bumps the pin in `go.mod`, since the manifests under `deploy/manifest/built-in-providers` are copies CI regenerates from it. ## File change summary | File | Summary of change | | ---- | ----------------- | | `pkg/schema/defaults.go` | New. `ApplyDefaults` walks a schema and fills absent properties from their declared `default`, returning the number applied. | | `pkg/schema/defaults_test.go` | New. Covers explicit values, required properties, read-only properties, nested objects, and deep copying of map and slice defaults. | | `pkg/dynamicrp/frontend/defaultsfilter.go` | New. `UpdateFilter` that fetches the resource type schema and applies defaults before the resource is saved, attaching the filled map only when something was applied. | | `pkg/dynamicrp/frontend/routes.go` | Register the defaults filter ahead of the encryption filter. | --------- Signed-off-by: Mike Azure <127820851+AzureMike@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Several resource types promise a default in prose without declaring one, and
sizeis the case in radius-project/radius#12532 — "Defaults toSif not provided" is English in adescription:field, and nothing reads it. This declares the real thing:sizeonredisCachesandpostgreSqlDatabases,versionanddatabaseonmySqlDatabases, anddatabaseonpostgreSqlDatabases.neo4jDatabasesis left alone, since its documented default is the resource name and a staticdefault:can't express that.Reason for change
Deploying one of these resources without setting the optional property puts a literal
{{context.resource.properties.size}}expression into the ARM request, because there's nothing for the recipe to substitute. Declaring the default is half the fix. The other half is radius-project/radius#12563, which makes a declareddefault:actually get applied — until that ships, nothing here changes behavior, which is why the two are separate and this one goes first.mySqlDatabasesversiondefaults to8.4, which is what the AWS Terraform recipe (Data/mySqlDatabases/recipes/aws/terraform/main.tf:31) and the Kubernetes Bicep recipe already fall back to whenversionis unset. Declaring8.0instead would have downgraded both, and a running RDS instance rejects a major version downgrade outright.Related to radius-project/radius#12532
How to test
Each declared default matches what the recipes already do when the property is unset, so the intended result is no change for anyone who was relying on the existing fallback:
redisCachessizeSsize == "S"branch of the Azure pack, which the description already documentedpostgreSqlDatabasessizeSpostgreSqlDatabasesdatabasepostgres_dbmySqlDatabasesdatabasemysql_dbmySqlDatabasesversion8.4try(var.version, "8.4")in the AWS recipe and?? '8.4'in the Kubernetes recipeWith radius-project/radius#12563 applied, an unset
sizeresolves toBalanced_B0on Redis andStandard_B1ms/Burstableon PostgreSQL, and an unsetdatabaseresolves topostgres_dbandmysql_db.File change summary
Data/redisCaches/redisCaches.yamldefault: 'S'onsize.Data/postgreSqlDatabases/postgreSqlDatabases.yamldefault: 'S'onsizeanddefault: postgres_dbondatabase.Data/mySqlDatabases/mySqlDatabases.yamldefault: mysql_dbondatabaseanddefault: '8.4'onversion, and reword theversiondescription from "Assumed to be 8.4" to match the other optional properties.